package com.bangcommunity.bbframe.dynconfig;

import com.bangcommunity.bbframe.common.base.MainConfigLoader;
import com.bangcommunity.bbframe.common.utils.io.PropertiesUtil;
import com.bangcommunity.bbframe.common.utils.lang.AnnotationUtils;
import com.bangcommunity.bbframe.common.utils.lang.StringUtils;
import com.bangcommunity.bbframe.common.utils.reflect.BeanUtils;
import com.bangcommunity.bbframe.common.utils.reflect.ClassUtils;
import com.bangcommunity.bbframe.common.utils.reflect.FieldUtils;
import com.bangcommunity.bbframe.common.utils.reflect.ReflectionUtils;
import com.bangcommunity.bbframe.dynconfig.annotation.ZkConf;
import com.bangcommunity.bbframe.dynconfig.annotation.ZkNodekey;
import com.bangcommunity.bbframe.dynconfig.parser.PropertyValueParserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Created by tanghc on 17/1/23.
 */
public class DynBeanProxy {
    static final Logger logger = LoggerFactory.getLogger(DynBeanProxy.class);
    private static ConcurrentMap<Class, Object> proxyBeanMap = new ConcurrentHashMap<>();

    private DynBeanProxy() {
    }

    private static class DynBeanProxyHolder {
        private static DynBeanProxy instance = new DynBeanProxy();
    }

    public static DynBeanProxy getInstance() {
        return DynBeanProxyHolder.instance;
    }

    public <E> E proxyConfigBean(final DynConfigClient client, final Class<E> clazz) {
        E o = (E) proxyBeanMap.get(clazz);
        if (null != o) {
            return o;
        }
        ZkConf zkConf = AnnotationUtils.findAnnotation(clazz, ZkConf.class);
        final String group = zkConf.group();
        final String dataId = zkConf.dataId();
        if (StringUtils.isBlank(group) || StringUtils.isBlank(dataId)) {
            logger.warn("zk dyn bean wrap fail not valid group or dataId ,bean={},group={},dataid={}", clazz, group,
                    dataId);
            o = BeanUtils.instantiate(clazz);
            proxyBeanMap.putIfAbsent(clazz, o);
            return o;
        }
        DynConfigRegister.getInstance().build(client, MainConfigLoader.getInstance().getProductCode(),
                MainConfigLoader.getInstance().getAppCode(), group, dataId, config -> {
                    logger.info(
                            "change config refresh dynbean in jvm productCode={},appCode={}, group={},dataid={},conf={}",
                            MainConfigLoader.getInstance().getProductCode(),
                            MainConfigLoader.getInstance().getAppCode(), group, dataId, config);
                    E oldO = (E) proxyBeanMap.get(clazz);
                    final E o1 = oldO != null ? oldO : BeanUtils.instantiate(clazz);
                    fillProxyBeanAndRefreshCache(client, config, o1);
                    if(ClassUtils.isAssignable(IDynConfigProcessor.class, o1.getClass())){
                        ((IDynConfigProcessor) o1).procesConfig(config);
                    }
                }, false);
        return (E) proxyBeanMap.get(clazz);

    }
    public void updateZkProperties(final DynConfigClient client, final Class<?> clazz,final IDynBeanPropertyReader dynBeanPropertyReader) throws Exception {
        ZkConf zkConf = AnnotationUtils.findAnnotation(clazz, ZkConf.class);
        final String group = zkConf.group();
        final String dataId = zkConf.dataId();
        if (StringUtils.isBlank(group) || StringUtils.isBlank(dataId)) {
            logger.warn("zk dyn bean wrap fail not valid group or dataId ,bean={},group={},dataid={}", clazz, group,
                    dataId);
            return ;
        }
        StringBuilder sbuild = new StringBuilder();
        ReflectionUtils.doWithFields(clazz, field -> {
            boolean b = dynBeanPropertyReader.validField(field);
            if(b){
                ZkNodekey zkNodekey = field.getAnnotation(ZkNodekey.class);
                String key = field.getName();
                String value = dynBeanPropertyReader.getValue(key);
                if (null != zkNodekey) {
                    key = zkNodekey.value();
                }
                sbuild.append(key).append("=").append(value).append("\n");
            }
        });
        String path = DynConfigClient.getPath(MainConfigLoader.getInstance().getProductCode(),
                MainConfigLoader.getInstance().getAppCode(), group, dataId);
        client.setConfig(path,sbuild.toString());

    }

    public <E> void fillProxyBeanAndRefreshCache(final DynConfigClient client, final String config, final E o) {
        fillProxyBean(client, config, o);
        proxyBeanMap.putIfAbsent(o.getClass(), o);
    }

    public <E> void fillProxyBean(final DynConfigClient client, final String config, final E o) {
        Class clazz = o.getClass();
        final Properties properties = PropertiesUtil.loadFromString(config);
        ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                ZkNodekey zkNodekey = field.getAnnotation(ZkNodekey.class);
                String key = field.getName();
                if (null != zkNodekey) {
                    key = zkNodekey.value();
                }
                if (ReflectionUtils.isFinal(field)) {
                    return;
                }
                if (Integer.class.equals(field.getType()) || Integer.TYPE.equals(field.getType())) {
                    writeField(field, o, PropertyValueParserUtil.getIntValue(properties, key,null));
                } else if (Long.class.equals(field.getType()) || Long.TYPE.equals(field.getType())) {
                    writeField(field, o, PropertyValueParserUtil.getLongValue(properties, key,null));
                } else if (Byte.class.equals(field.getType()) || Byte.TYPE.equals(field.getType())) {
                    writeField(field, o, PropertyValueParserUtil.getByteValue(properties, key,null));
                } else if (Boolean.class.equals(field.getType()) || Boolean.TYPE.equals(field.getType())) {
                    writeField(field, o, PropertyValueParserUtil.getBooleanValue(properties, key,null));
                } else if (Float.class.equals(field.getType()) || Float.TYPE.equals(field.getType())) {
                    writeField(field, o, PropertyValueParserUtil.getFloatValue(properties, key,null));
                } else if (Double.class.equals(field.getType()) || Double.TYPE.equals(field.getType())) {
                    writeField(field, o, PropertyValueParserUtil.getDoubleValue(properties, key,null));
                } else if (BigDecimal.class.equals(field.getType())) {
                    writeField(field, o, PropertyValueParserUtil.getBigDecimalValue(properties, key,null));
                } else {
                    writeField(field, o, PropertyValueParserUtil.getValue(properties, key,null));
                }
            }
        });
    }

    private void writeField(Field field, Object o, Object value) throws IllegalAccessException {
        ReflectionUtils.makeAccessible(field);
        int modifiers = field.getModifiers();

        if (Modifier.isStatic(modifiers)) {
            if (!Modifier.isPublic(modifiers)) {
                return;
            }
            FieldUtils.writeStaticField(o.getClass(), field.getName(), value);
        } else {
            FieldUtils.writeField(field, o, value);
        }

    }
}
